-- Copyright (c) 2024 Huawei Technologies Co., Ltd.
-- openUBMC is licensed under Mulan PSL v2.
-- You can use this software according to the terms and conditions of the Mulan PSL v2.
-- You may obtain a copy of Mulan PSL v2 at: http://license.coscl.org.cn/MulanPSL2
-- THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
-- EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
-- MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
-- See the Mulan PSL v2 for more details.

-- Description: 自描述文件（SR）升级服务
local skynet = require 'skynet'
local singleton = require 'mc.singleton'
local log = require 'mc.logging'
local client = require 'general_hardware.client'
local ctx = require 'mc.context'
local parser_cfg = require 'sr_upg_service.parser_cfg'
local c_sr_upgrade = require 'sr_upg_service.class.sr_upgrade'
local sr_upgrade_with_protect_chip = require 'sr_upg_service.class.sr_upgrade_with_protect_chip'
local vos = require 'utils.vos'
local cmn = require 'common'
local utils = require 'mc.utils'
local fw_manager = require 'fw_manager'
local upgrade_subject = require 'upgrade_subject.upgrade_subject'

local sr_upg_service = {}
sr_upg_service.__index = sr_upg_service

local RET_OK <const> = 0
local RET_ERR <const> = -1

local component_id_ex <const> = {['SR'] = 127}

function sr_upg_service:on_add_object(class_name, object, position)
    local switch = {
        ['SRUpgrade'] = function ()
            --fork防止获取不到对应器件的DeviceName
            cmn.skynet.fork(function ()
                -- Type为空视为无效对象
                if object.Type and object.Type ~= '' then
                    if next(object.WriteProtectChip) then
                        self.sr_upgrade_list[object.name] = sr_upgrade_with_protect_chip.new(object, self.bus, position)
                    else
                        self.sr_upgrade_list[object.name] = c_sr_upgrade.new(object, self.bus, position)
                    end
                end
            end)
        end
    }

    if switch[class_name] then
        switch[class_name]()
        log:notice('[SRUpgrade] add object, class: %s, position: %s', class_name, position)
    end
end

function sr_upg_service:on_delete_object(_, object, _)
    if self.sr_upgrade_list[object.name] then
        self.sr_upgrade_list[object.name]:unregister_firmware_info()
        self.sr_upgrade_list[object.name] = nil
    end
end

function sr_upg_service:on_add_object_complete(position)
    cmn.skynet.fork(function ()
        cmn.skynet.sleep(500) -- 等待5秒，防止获取失败
        for _, v in pairs(self.sr_upgrade_list) do
            -- 如果name为nil，说明Board对象晚于SRUpgrade对象分发，需要更新sr_upgrade的部分属性，再补注册
            if v.position == position and v.name == nil then
                v.name = fw_manager:get_device_name_by_position(v.mds_obj.Type, position) .. ' CSR'
                v:register_firmware_info()
            end
        end
    end)
end

function sr_upg_service:on_upgrade_prepare(context, system_id, firmware_type, cfg_path, _, parameters)
    log:notice('[sr_upg_service] prepare upgrade, firmware type: %s', firmware_type)
    local ok, rsp = client:PFileFileChown(ctx.new(), nil, cfg_path, 104, 104)  --设置配置文件属主为secbox
    if not ok then
        log:error('[SRUpgrade] chown cfg file failed, error %s', rsp)
        client:UpdateServiceUpdateServicePrepareReply(ctx.new(), system_id, firmware_type, '', RET_ERR, parameters)
        return
    end

    self.cfgs = {}
    local cfgs = parser_cfg.get_cfgs(cfg_path)
    for k, cfg in pairs(cfgs) do
        if cfg.component_id_ex == component_id_ex['SR'] then
            self.cfgs[k] = cfg
        end
    end
    if not next(self.cfgs) then
        log:error('[SRUpgrade] prepare SR upgrade failed,configs failed to get')
        return
    end

    self.active_mode = parameters["ActiveMode"] or "Immediately"
    log:notice('[SRUpgrade] ActiveMode: %s', self.active_mode)
    parameters.MultipleSupported = 'Support'
    client:UpdateServiceUpdateServicePrepareReply(ctx.new(), system_id, firmware_type, '', RET_OK, parameters)
end

-- 升级主任务
function sr_upg_service:upgrade_task(system_id, firmware_type, file_path, parameters)
    local ret = RET_ERR
    local s, e = string.find(file_path, "/.+/")
    local dir = string.sub(file_path, s, e)
    local name = string.sub(file_path, e + 1, -1)
    if not self.cfgs[name] then
        log:error('[SRUpgrade] file is not in upgrade configs, file path: %s', file_path)
        goto EXIT
    end
    -- 解压固件
    utils.secure_tar_unzip(
        file_path,
        dir,
        0x6400000, -- 最大解压限制100M
        1024
    )
    -- 遍历升级对象，匹配二进制文件
    client:UpdateServiceUpdateServiceUpdateUpgradeStatus(ctx.new(), system_id, firmware_type, 0, 20, 'Running',
        parameters)
    for _, sr_upgrade in pairs(self.sr_upgrade_list) do
        -- 使用UID拼接出二进制文件名
        local file_name = sr_upgrade.mds_obj.UID .. '.bin'
        local bin_path = dir .. file_name
        local old_version = sr_upgrade.fw_obj and sr_upgrade.fw_obj.Version or ''
        if vos.get_file_accessible(bin_path) then
            ret = RET_OK
            if not sr_upgrade:upgrade(bin_path) then
                ret = RET_ERR
                log:running(log.RLOG_ERROR, 'Upgrade %s Firmware (From: %s) failed', sr_upgrade.name, old_version)
                break
            end
            -- 修改firmware_mgmt资源树SR对象的ActiveMode属性
            sr_upgrade.fw_obj.ActiveMode = self.active_mode
            log:running(log.RLOG_INFO, 'Upgrade %s Firmware (From: %s) successfully', sr_upgrade.name, old_version)
        else
            log:error('[SRUpgrade] upgrade path can not get file, file_name:%s', file_name)
        end
    end
    ::EXIT::
    client:UpdateServiceUpdateServiceProcessReply(ctx.new(), system_id, firmware_type, ret, parameters)
end

function sr_upg_service:on_upgrade_process(context, system_id, firmware_type, file_path, parameters)
    if not next(self.cfgs) then
        log:info('[SRUpgrade] process SR upgrade failed, component or configs do not exist')
        return
    end
    log:notice('[SRUpgrade] process SR upgrade, firmware type: %s', firmware_type)
    local ok, rsp = client:PFileFileChown(ctx.new(), nil, file_path, 104, 104)
    if not ok then
        log:error('[SRUpgrade] chown file failed, error %s', rsp)
        client:UpdateServiceUpdateServiceProcessReply(ctx.new(), system_id, firmware_type, RET_ERR, parameters)
        return
    end

    skynet.fork(function ()
        ok, rsp = pcall(function()
            self:upgrade_task(system_id, firmware_type, file_path, parameters)
        end)
        if not ok then
            log:error('upgrade process stage failed, err: %s', rsp)
            client:UpdateServiceUpdateServiceProcessReply(ctx.new(), system_id, firmware_type, RET_ERR, parameters)
        end
    end)
end

local sr_active_register_list ={
    {Key = 'FirmwareId', Value = 'HWSR'},
    {Key = 'FirmwareType', Value = 'SR'},
    {Key = 'ActiveCondition', Value = 'None'},
    {Key = 'ActiveMode', Value = 'ResetBMC'},
    {Key = 'ActiveStatus', Value = 'Ready'}
}

local function sr_register_active_action(self)
    if self.active_mode == "ResetBMC" then
        sr_active_register_list[4] = {Key = 'ActiveMode', Value = 'DelayResetBMC'}
    else
        -- 将内存数据ActiveMode恢复为初始值，否则第二次升级使用的还是上一次的数据
        sr_active_register_list[4] = {Key = 'ActiveMode', Value = 'ResetBMC'}
    end

    client:FirmwareActiveFirmwareActiveRegisterActiveAction(ctx.new(), sr_active_register_list)
    log:notice('[SRUpgrade] Register active action successfully')
end

function sr_upg_service:on_upgrade_finish(context, system_id, firmware_type, parameters)
    if not next(self.cfgs) then
        log:info('[SRUpgrade] finish SR upgrade failed, component or configs do not exist')
        return
    end
    local ok, err = pcall(sr_register_active_action, self)
    if not ok then
        log:error('sr register active action failed, err: %s', err)
    end
    log:notice('[SRUpgrade] finish SR upgrade, firmware type: %s active_mode:%s', firmware_type, self.active_mode)
    client:UpdateServiceUpdateServiceFinishReply(ctx.new(), system_id, firmware_type, RET_OK, parameters)
end

function sr_upg_service.new()
    return setmetatable({}, sr_upg_service)
end

function sr_upg_service:init(bus)
    self.bus = bus
    self.sr_upgrade_list = {}

    self.cfgs = {}
    --延迟生效为新增功能，设定默认值为Immediately
    self.active_mode = "Immediately"

    -- 注册固件升级监测
    upgrade_subject.get_instance():register_upgrade_observer(self, 'HWSR')
end

return singleton(sr_upg_service)